using Microsoft.Maui.Controls;
using System;

namespace MauiApp.Views.Controls
{
    // Robust pinch-to-zoom container with pan and clamping, optimized for Android
    public class PinchToZoomContainer : ContentView
    {
        double startScale = 1;
        double currentScale = 1;
        double xOffset; // current translation cache
        double yOffset;
        double startX;  // translation at pan start
        double startY;
        bool isPinching;

        public static readonly BindableProperty MinScaleProperty = BindableProperty.Create(
            nameof(MinScale), typeof(double), typeof(PinchToZoomContainer), 0.5);

        public static readonly BindableProperty MaxScaleProperty = BindableProperty.Create(
            nameof(MaxScale), typeof(double), typeof(PinchToZoomContainer), 8.0);

        public double MinScale
        {
            get => (double)GetValue(MinScaleProperty);
            set => SetValue(MinScaleProperty, value);
        }

        public double MaxScale
        {
            get => (double)GetValue(MaxScaleProperty);
            set => SetValue(MaxScaleProperty, value);
        }

        public PinchToZoomContainer()
        {
            // Ensure the container receives touch input on Android
            BackgroundColor = Colors.Transparent;

            // Keep anchor fixed for stable math (top-left origin)
            Loaded += (_, __) =>
            {
                if (Content != null)
                {
                    Content.AnchorX = 0;
                    Content.AnchorY = 0;
                }
            };

            SizeChanged += (_, __) =>
            {
                if (Content != null)
                {
                    Content.AnchorX = 0;
                    Content.AnchorY = 0;
                }
            };

            var pinch = new PinchGestureRecognizer();
            pinch.PinchUpdated += OnPinchUpdated;
            GestureRecognizers.Add(pinch);

            var pan = new PanGestureRecognizer();
            pan.PanUpdated += OnPanUpdated;
            GestureRecognizers.Add(pan);
        }

        void OnPinchUpdated(object? sender, PinchGestureUpdatedEventArgs e)
        {
            if (Content == null)
                return;

            switch (e.Status)
            {
                case GestureStatus.Started:
                    isPinching = true;
                    startScale = Content.Scale;
                    // Cache current translation
                    xOffset = Content.TranslationX;
                    yOffset = Content.TranslationY;
                    break;

                case GestureStatus.Running:
                    // Guard invalid size
                    if (Width <= 0 || Height <= 0 || Content.Width <= 0 || Content.Height <= 0)
                        return;

                    var newScale = Math.Clamp(startScale * e.Scale, MinScale, MaxScale);

                    // Screen-space focal point (relative to container)
                    double fX = e.ScaleOrigin.X * Width;
                    double fY = e.ScaleOrigin.Y * Height;

                    // Content-space point under the fingers before scaling
                    double pX = (fX - xOffset) / startScale;
                    double pY = (fY - yOffset) / startScale;

                    // Apply scale
                    currentScale = newScale;
                    Content.Scale = currentScale;

                    // Compute translation so focal point stays under fingers
                    double targetX = fX - pX * currentScale;
                    double targetY = fY - pY * currentScale;

                    // Clamp to bounds
                    var bounds = GetBounds();
                    Content.TranslationX = Clamp(targetX, bounds.minX, bounds.maxX);
                    Content.TranslationY = Clamp(targetY, bounds.minY, bounds.maxY);
                    break;

                case GestureStatus.Completed:
                case GestureStatus.Canceled:
                    isPinching = false;
                    // Persist translation for next gesture
                    xOffset = Content.TranslationX;
                    yOffset = Content.TranslationY;
                    startScale = currentScale = Content.Scale;
                    break;
            }
        }

        void OnPanUpdated(object? sender, PanUpdatedEventArgs e)
        {
            if (Content == null)
                return;

            // Avoid fighting with pinch
            if (isPinching)
                return;

            switch (e.StatusType)
            {
                case GestureStatus.Started:
                    startX = Content.TranslationX;
                    startY = Content.TranslationY;
                    break;

                case GestureStatus.Running:
                    if (Content.Scale <= 1)
                        return; // no panning when not zoomed

                    var bounds = GetBounds();
                    var tx = Clamp(startX + e.TotalX, bounds.minX, bounds.maxX);
                    var ty = Clamp(startY + e.TotalY, bounds.minY, bounds.maxY);
                    Content.TranslationX = tx;
                    Content.TranslationY = ty;
                    break;

                case GestureStatus.Completed:
                case GestureStatus.Canceled:
                    xOffset = Content.TranslationX;
                    yOffset = Content.TranslationY;
                    break;
            }
        }

        (double minX, double maxX, double minY, double maxY) GetBounds()
        {
            // Allow content to move only within its scaled overflow
            if (Content == null || Width <= 0 || Height <= 0)
                return (0, 0, 0, 0);

            var scaledWidth = Content.Width * Content.Scale;
            var scaledHeight = Content.Height * Content.Scale;

            var maxX = 0d;
            var minX = Math.Min(0, Width - scaledWidth);
            var maxY = 0d;
            var minY = Math.Min(0, Height - scaledHeight);

            return (minX, maxX, minY, maxY);
        }

        static double Clamp(double value, double min, double max)
        {
            if (value < min) return min;
            if (value > max) return max;
            return value;
        }

        public void Reset()
        {
            if (Content == null) return;
            currentScale = startScale = 1;
            xOffset = yOffset = 0;
            Content.Scale = 1;
            Content.TranslationX = 0;
            Content.TranslationY = 0;
            Content.AnchorX = 0; // keep math consistent
            Content.AnchorY = 0;
        }
    }
}
